package redis

import (
	"encoding/json"
	"errors"
	"github.com/gomodule/redigo/redis"
	"sync"
)

// 实现 Session 中间件总接口
// RedisSession 结构体
type RedisSession struct {
	sessionID  string
	pool       *redis.Pool
	sessionMap map[string]interface{} // 先将session放入内存，然后一起放入redis
	rwlock     sync.RWMutex           // 读写锁
	flag       int                    // 记录内存中的map是否被操作
}

// 用常量去定义状态
const (
	SessionFlagNone   = iota // 内存数量无变化
	SessionFlagModify        // 有变化
)

// NewRedisSession 构造函数
func NewRedisSession(id string, pool *redis.Pool) *RedisSession {
	return &RedisSession{
		sessionID:  id,
		pool:       pool,
		sessionMap: make(map[string]interface{}, 16),
		flag:       SessionFlagNone,
	}
}

// Set 吧session 存储到内存中
func (r *RedisSession) Set(key string, value interface{}) (err error) {
	// 加锁
	r.rwlock.Lock()
	defer r.rwlock.Unlock()
	// 设置值
	r.sessionMap[key] = value
	// 标记变更
	r.flag = SessionFlagModify
	return
}

// Save 吧session 存到 redis中
func (r *RedisSession) Save() (err error) {
	// 加锁
	r.rwlock.Lock()
	defer r.rwlock.Unlock()
	// 若数据没变化，不需要存
	if r.flag != SessionFlagModify {
		return
	}
	// 将内存中的 sessionMao 进行序列化存到redis
	data, err := json.Marshal(r.sessionMap)
	if err != nil {
		return
	}
	// 获取redis链接
	conn := r.pool.Get()
	_, err = conn.Do("SET", r.sessionID, string(data))
	if err != nil {
		return
	}
	r.flag = SessionFlagNone
	return
}

// Get
func (r *RedisSession) Get(key string) (result interface{}, err error) {
	// 加锁
	r.rwlock.Lock()
	defer r.rwlock.Unlock()
	// 取值
	result, ok := r.sessionMap[key]
	if !ok {
		err = errors.New("key not exists")
		return
	}
	return
}

// redis再次加载
func (r *RedisSession) loadFromRedis() (err error) {
	conn := r.pool.Get()
	reply, err := conn.Do("GET", r.sessionID)
	if err != nil {
		return
	}
	data, err := redis.String(reply, err)
	if err != nil {
		return
	}
	err = json.Unmarshal([]byte(data), &r.sessionMap)
	if err != nil {
		return
	}
	return
}

// Del
func (r *RedisSession) Del(key string) (err error) {
	r.rwlock.Lock()
	defer r.rwlock.Unlock()
	delete(r.sessionMap, key)
	return
}
